(ns minesweeper
 (:use ref.grid))

(defstruct cell :has-bomb?)

(defn- cell-has-bomb? [game x y]
  (:has-bomb? (val-at  game x y)))

(defn- mine-count-in-region [game min-x max-x min-y max-y]
  (apply 
    + 
    (every-cell-in-range min-x max-x min-y max-y 
      #(if (cell-has-bomb? game %1 %2) 1 0))))

(defn mine-count [game]
  (mine-count-in-region game 0 (width game) 0 (height game)))

(defn- create-empty-board [width height]
  (create-ref-grid width height (struct cell false)))

(defn- plant-mine 
  ([game]
    (plant-mine game (rand-int (width game)) (rand-int (height game))))
  ([game x y]
    (alter
      (ref-at game x y)
      #(assoc %1 :has-bomb? true))))

(defn- plant-mines [game number-of-mines]
  (dosync    
    (loop []    
      (when (< (mine-count game) number-of-mines)
        (plant-mine game)
        (recur)))))

(defn create-game [width height number-of-mines]
  (let [game (create-empty-board width height)]
    (plant-mines game number-of-mines)
    game))

(defn create-game-with [& cells]
  (let [game (create-empty-board (count (first cells)) (count cells))]
    (dosync
      (doall (every-cell-in-range (width game) (height game)
               (fn [x y] 
                 (if (= 1 (nth (nth cells y) x))
                   (plant-mine game x y))))))
    game))

(defn- count-mines-in-proximity [game x y]
  (let [min-x (max 0 (dec x))
        max-x (min (width game) (+ 2 x))
        min-y (max 0 (dec y))
        max-y (min (height game) (+ 2 y))]        
    
    (mine-count-in-region game min-x max-x min-y max-y)))

(defn- mark-cell-as-revealed [game x y]
  (dosync
    (alter
      (ref-at game x y)
      #(assoc %1 :revealed? true))))

(defn- all-non-mine-cells-revealed? [game]
  (every-value? 
    #(or
       (:revealed? %1)
       (:has-bomb? %1))
    game))

(defn reveal [game x y]  
  (if (cell-has-bomb? game x y)
    :BOOM-GAME-OVER
    (do
      (mark-cell-as-revealed game x y)
      (if (all-non-mine-cells-revealed? game)
        :YOU-WIN
        (count-mines-in-proximity game x y)))))

(defn print-board [game]
  (println "======")
  (dotimes [y (height game)]
    (dotimes [x (width game)]
      (print
        (cond
          (cell-has-bomb? game x y) "!"
          (:revealed? (val-at game x y)) "-"
          true "?")))
    (println))
  (println "======"))